﻿/*
Copyright (c) 2009 Vratislav Cermak (vratacermak@gmail.com)
 
This file is part of TeachMe Project. See http://code.google.com/p/teach-me for more information.
 
TeachMe is an open source project. Using of TeachMe is free of charge.
It is distributed under GNU General Public License version 3 (GPLv3).
GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using TeachMe.Application.Data;
using TeachMe.Application.Drawing;
using TeachMe.Application.Events;
using TeachMe.Application.Exceptions;
using TeachMe.Application.Forms;
using TeachMe.Application.Properties;
using TeachMe.Application.Themes;
using ButtonState = TeachMe.Application.Drawing.ButtonState;

namespace TeachMe.Application
{
    public class DisplayManager
    {
        private class EntriesFormWrapper
        {
            public event EventHandler<MouseEventArgs> MouseMove;
            public event EventHandler<EventArgs> EscapeDown;

            private readonly EntriesForm _form;

            public EntriesFormWrapper()
            {
                _form = new EntriesForm();
                _form.MouseMove += OnMouseMove;
                _form.EscapeDown += OnEscapeDown;
                _form.Show();
            }

            private void OnEscapeDown(object sender, EventArgs e)
            {
                EventHandler<EventArgs> handler = EscapeDown;
                if (handler != null)
                {
                    handler(sender, e);
                }
            }

            void OnMouseMove(object sender, MouseEventArgs e)
            {
                EventHandler<MouseEventArgs> handler = MouseMove;
                if (handler != null)
                {
                    handler(sender, e);
                }
            }

            public Graphics CreateGraphics()
            {
                return _form.CreateGraphics();
            }

            public Size Size
            {
                get
                {
                    return _form.Size;
                }
                set
                {
                    _form.Size = value;
                }
            }

            public Point Location
            {
                get
                {
                    return _form.Location;
                }
                set
                {
                    _form.Location = value;
                }
            }

            public void UpdateBitmapBuffer(Bitmap bitmap, Point location, int opacity)
            {
                _form.UpdateBitmapBuffer(bitmap, location, opacity);
            }

            public void ClearBitmapBuffer()
            {
                _form.ClearBitmapBuffer();
            }

            public void Draw()
            {
                _form.Draw();
            }

            public bool TopMost
            {
                set
                {
                    _form.TopMost = value;
                }
            }

            public void ShowEntries()
            {
                _form.Activate();
            }
        }
        private readonly List<EntryDescriptor> _entries;
        private readonly Root _root;
        private DrawContextBuilder _drawContextBuilder;
        private Timer _hintTimer;
        private readonly EntriesFormWrapper _entriesFormWrapper;
        private readonly MouseManager _mouseManager;
        private Point _initialMovingFormLocation;
        private bool _isMoving;
        
        public const int MinumumVisiblePartOfWindow = 10;

        public event EventHandler<EventArgs> EntryClosed;

        public DisplayManager(Root root)
        {
            _root = root;
            _root.DictionaryEntriesModified += OnDictionaryEntriesModified;
            _root.DictionaryRemoved += OnDictionaryRemoved;
            _root.DictionaryUserSettingsModified += OnDictionaryUserSettingsModified;
            
            _entries = new List<EntryDescriptor>();
            
            _entriesFormWrapper = new EntriesFormWrapper();
            _entriesFormWrapper.Location = _root.MachineSettings.FirstEntryLocation;
            _entriesFormWrapper.TopMost = _root.UserSettings.TopMostEntries.Value;
            _entriesFormWrapper.MouseMove += OnMouseMove;
            _entriesFormWrapper.EscapeDown += OnEscapeDown;

            _mouseManager = new MouseManager(this);
            _mouseManager.EntryMouseWheel += OnEntryMouseWheel;
            _mouseManager.EntryEvent += OnEntryEvent;
            _mouseManager.EntryClicked += OnEntryClicked;
            _mouseManager.EntryButtonEvent += OnEntryButtonEvent;
            _mouseManager.EntryButtonClicked += OnEntryButtonClicked;
            _mouseManager.EntryButtonPressed += OnEntryButtonPressed;
            _mouseManager.Moving += OnMoving;
            _mouseManager.MovingStarted += OnMovingStarted;
            _mouseManager.MovingStopped += OnMovingStopped;

            _root.UserSettings.EntriesHorizontalDistance.Changed += OnEntriesDistanceChanged;
            _root.UserSettings.EntriesVerticalDistance.Changed += OnEntriesDistanceChanged;
            _root.UserSettings.EntryOpacity.Changed += OnEntryOpacityChanged;
            _root.UserSettings.TopMostEntries.Changed += OnTopMostEntriesChanged;
            _root.UserSettings.EntryRevealReason.Changed += OnEntryRevealReasonChanged;
            _root.UserSettings.HighlightFromInExample.Changed += new EventHandler<EventArgs>(OnHighlightFromInExampleChanged);
            
            InitializeTimers();
        }

        void OnHighlightFromInExampleChanged(object sender, EventArgs e)
        {
            ClearBitmapBuffer();
            UpdateEntriesBitmaps();
            Draw();
        }

        void OnEntryRevealReasonChanged(object sender, EventArgs e)
        {
            // reveal when reveal reason has been changed

            foreach (EntryDescriptor descriptor in _entries)
            {
                descriptor.DisplayedEntryState.EntryPhase = DisplayedEntryPhase.Revelead;
            }

            ClearBitmapBuffer();
            UpdateEntriesBitmaps();
            Draw();

            //

            ResetHintTimer();
        }

        void OnEntryMouseWheel(object sender, EntryEventArgs e)
        {
            _root.DisplayEntry();

            // mouse manager is not enabled again because i don't get mouse move from form
            _mouseManager.Enabled = true;
        }

        void OnEscapeDown(object sender, EventArgs e)
        {
            if (_isMoving)
            {
                _entriesFormWrapper.Location = _initialMovingFormLocation;
                _mouseManager.StopMoving();

                HandleChangedPosition(_initialMovingFormLocation);
            }
        }

        void OnTopMostEntriesChanged(object sender, EventArgs e)
        {
            _entriesFormWrapper.TopMost = _root.UserSettings.TopMostEntries.Value;
        }

        void OnEntryOpacityChanged(object sender, EventArgs e)
        {
            foreach (EntryDescriptor descriptor in _entries)
            {
                descriptor.Opacity = _root.UserSettings.EntryOpacity.Value;
            }

            ClearBitmapBuffer();
            UpdateEntriesBitmaps();
            Draw();
        }

        void OnEntriesDistanceChanged(object sender, EventArgs e)
        {
            Relocate();
            ClearBitmapBuffer();
            UpdateEntriesBitmaps();
            Draw();
        }

        void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (!_mouseManager.Enabled)
            {
                Logging.Debug(GetType(), "Enabling mouse manager.");

                _mouseManager.Enabled = true;
            }
        }

        void OnDictionaryUserSettingsModified(object sender, DictionaryEventArgs e)
        {
            if (!e.Dictionary.UserData.Enabled)
            {
                RemoveEntriesFromDictionary(e.Dictionary);
            }
        }

        private void RemoveEntriesFromDictionary(Dictionary dictionary)
        {
            List<EntryDescriptor> toRemove = new List<EntryDescriptor>();

            foreach (EntryDescriptor descriptor in _entries)
            {
                if (descriptor.Dictionary == dictionary)
                {
                    toRemove.Add(descriptor);
                }
            }

            if (toRemove.Count > 0)
            {
                foreach (EntryDescriptor descriptor in toRemove)
                {
                    _entries.Remove(descriptor);
                }

                Relocate();
                ClearBitmapBuffer();
                UpdateEntriesBitmaps();
                Draw();
                RefreshMouseManager();
                FireEntryClosed();
            }
        }

        void OnDictionaryRemoved(object sender, DictionaryEventArgs e)
        {
            RemoveEntriesFromDictionary(e.Dictionary);
        }

        void OnDictionaryEntriesModified(object sender, DictionaryEntriesEventArgs e)
        {
            List<EntryDescriptor> toRemove = new List<EntryDescriptor>();
            bool repaint = false;

            foreach (EntryDescriptor descriptor in _entries)
            {
                if (e.Dictionary == descriptor.Dictionary)
                {
                    if (e.Entries.ContainsKey(descriptor.EntryId))
                    {
                        switch (e.Entries[descriptor.EntryId])
                        {
                            case EditedItemState.Normal:
                                descriptor.DrawingContext = _drawContextBuilder.CreateEntryDrawingContext(descriptor);
                                UpdatePriorityButtonsState(descriptor);
                                repaint = true;
                                break;
                            case EditedItemState.Deleted:
                                toRemove.Add(descriptor);
                                repaint = true;
                                break;
                            case EditedItemState.New:
                                break;
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                    }
                }
            }

            foreach (EntryDescriptor descriptor in toRemove)
            {
                _entries.Remove(descriptor);
            }

            if (repaint)
            {
                Relocate();
                ClearBitmapBuffer();
                UpdateEntriesBitmaps();
                Draw();
                RefreshMouseManager();
                FireEntryClosed();
            }
        }

        private void OnEntryButtonPressed(object sender, EntryButtonPressedEventArgs e)
        {
            switch (e.ButtonType)
            {
                case EntryButtonType.Close:
                    e.EntryDescriptor.DisplayedEntryState.ButtonsStates[EntryButtonType.Close] = ButtonState.Pressed;
                    UpdateEntryBitmap(e.EntryDescriptor);
                    Draw();
                    break;
                case EntryButtonType.VeryLow:
                case EntryButtonType.Low:
                case EntryButtonType.Normal:
                case EntryButtonType.High:
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private void UpdateEntriesBitmaps()
        {
            foreach (EntryDescriptor descriptor in _entries)
            {
                UpdateEntryBitmap(descriptor);
            }
        }

        private void Draw()
        {
            _entriesFormWrapper.Draw();
        }

        private void UpdateEntryBitmap(EntryDescriptor descriptor)
        {
            if (!descriptor.Visible || !_entries.Contains(descriptor))
            {
                return;
            }

            using (Bitmap cache = new Bitmap(descriptor.DrawingContext.Size.Width, descriptor.DrawingContext.Size.Height))
            {
                using (Graphics cacheGraphics = Graphics.FromImage(cache))
                {
                    descriptor.Draw(cacheGraphics, descriptor.DisplayedEntryState.EntryPhase == DisplayedEntryPhase.Counting ? GetHintProgress(descriptor) : 0, _root.UserSettings.HighlightFromInExample.Value);
                }

                _entriesFormWrapper.UpdateBitmapBuffer(cache, descriptor.RelativeLocation, descriptor.Opacity);
            }
        }

        private int GetHintProgress(EntryDescriptor descriptor)
        {
            double duration = _root.UserSettings.HintInterval.Value * 1000;
            double dispTime = (DateTime.Now - descriptor.Created).TotalMilliseconds;
            int result =  dispTime > 0 ? (int)(dispTime / (duration / 100)) : 0;

            return Math.Min(100, result);
        }

        private void OnEntryButtonClicked(object sender, EntryButtonClickedEventArgs e)
        {
            if (e.MouseButtons == MouseButtons.Left)
            {
                if (e.ButtonType == EntryButtonType.Close)
                {
                    CloseEntry(e.EntryDescriptor);
                }
                else
                {
                    Priority newPriority;

                    switch (e.ButtonType)
                    {
                        case EntryButtonType.VeryLow:
                            newPriority = Priority.VeryLow;
                            break;
                        case EntryButtonType.Low:
                            newPriority = Priority.Low;
                            break;
                        case EntryButtonType.Normal:
                            newPriority = Priority.Normal;
                            break;
                        case EntryButtonType.High:
                            newPriority = Priority.High;
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }

                    try
                    {
                        e.EntryDescriptor.Dictionary.SetEntryPriority(e.EntryDescriptor.EntryId, newPriority);
                    }
                    catch (DictionaryException exception)
                    {
                        Logging.Error(GetType(), exception, "Dictionary could not be saved.");

                        MessageBox.Show(Resources.DictionarySaveError, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }

                    UpdatePriorityButtonsState(e.EntryDescriptor);
                    UpdateEntryBitmap(e.EntryDescriptor);
                    Draw();
                }
            }
        }

        private void OnEntryClicked(object sender, EntryClickedEventArgs e)
        {
            if (_root.ContextMenusManager.ContextMenuOpened)
            {
                return;
            }

            if (e.MouseButtons == MouseButtons.Left)
            {
                switch (e.EntryDescriptor.DisplayedEntryState.EntryPhase)
                {
                    case DisplayedEntryPhase.Zombie:
                        e.EntryDescriptor.DisplayedEntryState.EntryPhase = DisplayedEntryPhase.Revelead;
                        UpdateEntryBitmap(e.EntryDescriptor);
                        Draw();
                        break;
                    case DisplayedEntryPhase.Counting:
                        e.EntryDescriptor.DisplayedEntryState.EntryPhase = DisplayedEntryPhase.Revelead;
                        UpdateEntryBitmap(e.EntryDescriptor);
                        Draw();
                        break;
                    case DisplayedEntryPhase.Revelead:
                        CloseEntry(e.EntryDescriptor);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
            else if (e.MouseButtons == MouseButtons.Right)
            {
                _root.ContextMenusManager.SetCurrentEntryDescriptor(e.EntryDescriptor);
                _root.ContextMenusManager.Get(ContextMenuType.Entry).Show(Control.MousePosition);
            }
            else if (e.MouseButtons == MouseButtons.Middle)
            {
                _root.DisplayEntry();
            }
        }

        private void OnMovingStopped(object sender, EventArgs e)
        {
            _isMoving = false;

            using (_root.MachineSettings.Edit())
            {
                _root.MachineSettings.FirstEntryLocation = _entriesFormWrapper.Location;
            }

            _mouseManager.Refresh();
        }

        private void OnMovingStarted(object sender, MovingStartedEventArgs e)
        {
            _isMoving = true;

            _initialMovingFormLocation = _entriesFormWrapper.Location;
        }

        private void OnMoving(object sender, MovingEventArgs e)
        {
            HandleChangedPosition(Utilities.AddUpLocations(_initialMovingFormLocation, Utilities.SubstractLocations(e.CurrentLocation, e.StartLocation)));
        }

        private void HandleChangedPosition(Point newLocation)
        {
            WindowLocations windowLocations = new WindowLocations();
            
            // up, left
            windowLocations.Populate(newLocation.X - MinumumVisiblePartOfWindow, newLocation.Y - MinumumVisiblePartOfWindow, _entries[0].DrawingContext.Size);
            if (!windowLocations.IsPartiallyVisible())
            {
                return;
            }

            // bottom, right
            windowLocations.Populate(newLocation.X + MinumumVisiblePartOfWindow, newLocation.Y + MinumumVisiblePartOfWindow, _entries[0].DrawingContext.Size);
            if (!windowLocations.IsPartiallyVisible())
            {
                return;
            }

            _entriesFormWrapper.Location = newLocation;

            if (Relocate())
            {
                ClearBitmapBuffer();
                UpdateEntriesBitmaps();
                Draw();
            }
        }

        private void OnEntryButtonEvent(object sender, EntryButtonEventArgs e)
        {
            if (e.EventType == EntryButtonEventType.Enter)
            {
                switch (e.ButtonType)
                {
                    case EntryButtonType.Close:
                        e.EntryDescriptor.DisplayedEntryState.ButtonsStates[e.ButtonType] = e.Pressed ? ButtonState.Pressed : ButtonState.Hover;
                        break;
                    case EntryButtonType.VeryLow:
                        UpdatePriorityButtonsState(e.EntryDescriptor, Priority.VeryLow);
                        break;
                    case EntryButtonType.Low:
                        UpdatePriorityButtonsState(e.EntryDescriptor, Priority.Low);
                        break;
                    case EntryButtonType.Normal:
                        UpdatePriorityButtonsState(e.EntryDescriptor, Priority.Normal);
                        break;
                    case EntryButtonType.High:
                        UpdatePriorityButtonsState(e.EntryDescriptor, Priority.High);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
            else if (e.EventType == EntryButtonEventType.Leave)
            {
                switch (e.ButtonType)
                {
                    case EntryButtonType.Close:
                        e.EntryDescriptor.DisplayedEntryState.ButtonsStates[EntryButtonType.Close] = ButtonState.Normal;
                        break;
                    case EntryButtonType.VeryLow:
                    case EntryButtonType.Low:
                    case EntryButtonType.Normal:
                    case EntryButtonType.High:
                        UpdatePriorityButtonsState(e.EntryDescriptor);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }

            UpdateEntryBitmap(e.EntryDescriptor);
            Draw();
        }

        private void OnEntryEvent(object sender, EntryEventArgs e)
        {
            Logging.Debug(GetType(), "Entry event '{0}' occured.", e.EventType);

            if (e.EventType == EntryEventType.Leave)
            {
                _mouseManager.Enabled = false;
            }

            if (_root.UserSettings.EntryOpacity.Value == 255)
            {
                return;
            }

            switch (e.EventType)
            {
                case EntryEventType.Enter:
                    e.EntryDescriptor.Opacity = 255;
                    UpdateEntryBitmap(e.EntryDescriptor);
                    Draw();
                    break;
                case EntryEventType.Leave:
                    e.EntryDescriptor.Opacity = _root.UserSettings.EntryOpacity.Value;
                    UpdateEntryBitmap(e.EntryDescriptor);
                    Draw();
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private void InitializeTimers()
        {
            _hintTimer = new Timer();
            _hintTimer.Tick += OnHintTimerTick;

            ResetHintTimer();
        }

        public void ResetHintTimer()
        {
            _hintTimer.Enabled = _root.UserSettings.EntryRevealReason.Value == EntryRevealReason.Interval;
            _hintTimer.Interval = 100;
        }

        private void OnHintTimerTick(object sender, EventArgs e)
        {
            bool hintUpdated = false;

            foreach (EntryDescriptor entryDescriptor in _entries)
            {
                if (!_isMoving && entryDescriptor.DisplayedEntryState.EntryPhase == DisplayedEntryPhase.Counting)
                {
                    if (entryDescriptor.DisplayedEntryState.EntryPhase == DisplayedEntryPhase.Counting && GetHintProgress(entryDescriptor) >= 100)
                    {
                        entryDescriptor.DisplayedEntryState.EntryPhase = DisplayedEntryPhase.Revelead;
                    }

                    UpdateEntryBitmap(entryDescriptor);

                    hintUpdated = true;
                }
            }

            if (hintUpdated)
            {
                Draw();
            }
        }

        public EntryDescriptor GetEntryDescriptorOnLocation(Point location)
        {
            foreach (EntryDescriptor entryDescriptor in _entries)
            {
                if (entryDescriptor.IsOnEntry(location))
                {
                    return entryDescriptor;
                }
            }

            return null;
        }

        private static void UpdatePriorityButtonsState(EntryDescriptor descriptor)
        {
            UpdatePriorityButtonsState(descriptor, descriptor.Dictionary.GetEntryPriority(descriptor.EntryId));
        }

        private static void UpdatePriorityButtonsState(EntryDescriptor descriptor, Priority priority)
        {
            switch (priority)
            {
                case Priority.VeryLow:
                    descriptor.SetButtonState(EntryButtonType.VeryLow, ButtonState.Down);
                    descriptor.SetButtonState(EntryButtonType.Low, ButtonState.Normal);
                    descriptor.SetButtonState(EntryButtonType.Normal, ButtonState.Normal);
                    descriptor.SetButtonState(EntryButtonType.High, ButtonState.Normal);
                    break;
                case Priority.Low:
                    descriptor.SetButtonState(EntryButtonType.VeryLow, ButtonState.Down);
                    descriptor.SetButtonState(EntryButtonType.Low, ButtonState.Down);
                    descriptor.SetButtonState(EntryButtonType.Normal, ButtonState.Normal);
                    descriptor.SetButtonState(EntryButtonType.High, ButtonState.Normal);
                    break;
                case Priority.Normal:
                    descriptor.SetButtonState(EntryButtonType.VeryLow, ButtonState.Down);
                    descriptor.SetButtonState(EntryButtonType.Low, ButtonState.Down);
                    descriptor.SetButtonState(EntryButtonType.Normal, ButtonState.Down);
                    descriptor.SetButtonState(EntryButtonType.High, ButtonState.Normal);
                    break;
                case Priority.High:
                    descriptor.SetButtonState(EntryButtonType.VeryLow, ButtonState.Down);
                    descriptor.SetButtonState(EntryButtonType.Low, ButtonState.Down);
                    descriptor.SetButtonState(EntryButtonType.Normal, ButtonState.Down);
                    descriptor.SetButtonState(EntryButtonType.High, ButtonState.Down);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private bool Relocate()
        {
            Size newSize = Relocate(_entriesFormWrapper.Location);

            if (newSize != _entriesFormWrapper.Size)
            {
                _entriesFormWrapper.Size = newSize;

                return true;
            }

            return false;
        }

        private static bool IsEntryPartiallyVisible(int left, int top, Size size)
        {
            WindowLocations wl = new WindowLocations();
            wl.Populate(left, top, size);
            return wl.IsPartiallyVisible();
        }

        private Size Relocate(Point initialLocation)
        {
            if (_entries.Count == 0)
            {
                return new Size();
            }

            WindowLocations windowLocations = new WindowLocations(initialLocation.X, initialLocation.Y);

            int left = initialLocation.X;
            int top = initialLocation.Y;

            // when invisible entry is found than we do not display next entries as well
            bool invisibleEntryFound = false;

            foreach (EntryDescriptor descriptor in _entries)
            {
                if (invisibleEntryFound)
                {
                    descriptor.Visible = false;
                    continue;
                }

                descriptor.Visible = true;

                windowLocations.Populate(left, top, descriptor.DrawingContext.Size);

                if (descriptor == _entries[0])
                {
                    if (!IsEntryPartiallyVisible(left, top, descriptor.DrawingContext.Size))
                    {
                        // first entry must be fully partially visible, otherwise reset position
                        
                        _entriesFormWrapper.Location = new Point(0, 0);
                        return Relocate(_entriesFormWrapper.Location);
                    }
                }
                else
                {
                    if (!windowLocations.IsVisible(WindowEdge.BottomLeft) && !windowLocations.IsVisible(WindowEdge.BottomRight))
                    {
                        left = left + _entries[0].Rectangle.Width + _root.UserSettings.EntriesHorizontalDistance.Value;
                        top = initialLocation.Y;

                        windowLocations.Populate(left, top, descriptor.DrawingContext.Size);

                        if (!windowLocations.IsFullyVisible() && !windowLocations.IsVisible(WindowEdge.TopRight))
                        {
                            descriptor.Visible = false;
                            invisibleEntryFound = true;

                            continue;
                        }
                    }
                }

                descriptor.AbsoulteLocation = new Point(left, top);
                descriptor.RelativeLocation = new Point(left - _entriesFormWrapper.Location.X, top - _entriesFormWrapper.Location.Y);

                top += (descriptor.DrawingContext.Size.Height + _root.UserSettings.EntriesVerticalDistance.Value);
            }

            return GetEntriesFormSize(initialLocation);
        }

        private void ClearBitmapBuffer()
        {
            _entriesFormWrapper.ClearBitmapBuffer();
        }

        private void FireEntryClosed()
        {
            EventHandler<EventArgs> handler = EntryClosed;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }

        private void RefreshMouseManager()
        {
            _mouseManager.Refresh();
        }

        private Size GetEntriesFormSize(Point initialLocation)
        {
            int entryWidth = _entries[0].Rectangle.Width;
            int lastLeft = initialLocation.X;
            int height = 0;
            int width = entryWidth;
            int maxHeight = 0;

            foreach (EntryDescriptor descriptor in _entries)
            {
                if (!descriptor.Visible)
                {
                    break;
                }
                
                if (lastLeft != descriptor.Rectangle.Left)
                {
                    // new column, test if last column was highest
                    maxHeight = Math.Max(maxHeight, height);
                    height = 0;

                    width += (entryWidth + _root.UserSettings.EntriesHorizontalDistance.Value);
                }

                lastLeft = descriptor.Rectangle.Left;

                if (descriptor == _entries[0])
                {
                    height += descriptor.DrawingContext.Size.Height;
                }
                else
                {
                    height += (_root.UserSettings.EntriesVerticalDistance.Value + descriptor.DrawingContext.Size.Height);
                }
            }
            
            return new Size(width, Math.Max(maxHeight, height));
        }

        private void AddEntryInternally(NewEntryDescriptorData newEntryDescriptorData)
        {
            // descriptor
            EntryDescriptor entryDescriptor = new EntryDescriptor(newEntryDescriptorData);

            entryDescriptor.Visible = true;
            entryDescriptor.Opacity = _root.UserSettings.EntryOpacity.Value;
            entryDescriptor.DisplayedEntryState = new DisplayedEntryState();

            switch (_root.UserSettings.EntryRevealReason.Value)
            {
                case EntryRevealReason.Immediate:
                    entryDescriptor.DisplayedEntryState.EntryPhase = DisplayedEntryPhase.Revelead;
                    break;
                case EntryRevealReason.Click:
                    entryDescriptor.DisplayedEntryState.EntryPhase = DisplayedEntryPhase.Zombie;
                    break;
                case EntryRevealReason.Interval:
                    entryDescriptor.DisplayedEntryState.EntryPhase = DisplayedEntryPhase.Counting;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }


            entryDescriptor.DrawingContext = _drawContextBuilder.CreateEntryDrawingContext(entryDescriptor);

            // entry buttons state
            entryDescriptor.DisplayedEntryState.ButtonsStates = new Dictionary<EntryButtonType, ButtonState>();
            entryDescriptor.AddButtonState(EntryButtonType.Close, ButtonState.Normal);
            entryDescriptor.AddButtonState(EntryButtonType.VeryLow, ButtonState.Normal);
            entryDescriptor.AddButtonState(EntryButtonType.Low, ButtonState.Normal);
            entryDescriptor.AddButtonState(EntryButtonType.Normal, ButtonState.Normal);
            entryDescriptor.AddButtonState(EntryButtonType.High, ButtonState.Normal);

            UpdatePriorityButtonsState(entryDescriptor);

            _entries.Add(entryDescriptor);
        }

        public int EntriesCount
        {
            get { return _entries.Count; }
        }

        public bool TopMostEntries
        {
            set
            {
                _entriesFormWrapper.TopMost = value;
            }
        }

        public void AddEntry(NewEntryDescriptorData entry)
        {
            AddEntryInternally(entry);

            Relocate();
            ClearBitmapBuffer();
            UpdateEntriesBitmaps();
            Draw();
            RefreshMouseManager();
        }

        public void AddEntries(List<NewEntryDescriptorData> entries)
        {
            foreach (NewEntryDescriptorData newEntryDescriptorData in entries)
            {
                AddEntryInternally(newEntryDescriptorData);
            }

            Relocate();
            ClearBitmapBuffer();
            UpdateEntriesBitmaps();
            Draw();
            RefreshMouseManager();
        }

        public void CloseEntries()
        {
            _entries.Clear();

            Relocate();
            ClearBitmapBuffer();
            UpdateEntriesBitmaps();
            Draw();
            RefreshMouseManager();
            FireEntryClosed();
        }

        public void SetNumberOfDisplayedEntries(int count)
        {
            while (_entries.Count > count)
            {
                _entries.RemoveAt(0);
            }

            Relocate();
            ClearBitmapBuffer();
            UpdateEntriesBitmaps();
            Draw();
            RefreshMouseManager();
            FireEntryClosed();
        }

        public void RemoveFirstEntry()
        {
            _entries.RemoveAt(0);

            Relocate();
            ClearBitmapBuffer();
            UpdateEntriesBitmaps();
            Draw();
            RefreshMouseManager();
            FireEntryClosed();
        }

        public void CloseEntry(EntryDescriptor entryDescriptor)
        {
            _entries.Remove(entryDescriptor);

            Relocate();
            ClearBitmapBuffer();
            UpdateEntriesBitmaps();
            Draw();
            RefreshMouseManager();
            FireEntryClosed();
        }

        public void SetTheme(Theme theme)
        {
            _drawContextBuilder = new DrawContextBuilder(theme, _entriesFormWrapper.CreateGraphics(), _root.UserSettings);

            foreach (EntryDescriptor entryDescriptor in _entries)
            {
                entryDescriptor.DrawingContext = _drawContextBuilder.CreateEntryDrawingContext(entryDescriptor);
            }

            Relocate();
            ClearBitmapBuffer();
            UpdateEntriesBitmaps();
            Draw();
            RefreshMouseManager();
        }

        public void ShowEntries()
        {
            _entriesFormWrapper.ShowEntries();
        }
    }
}
